package com.supreme.threadserver.controller;

import com.supreme.commonboot.utils.AjaxResp;
import com.supreme.commonboot.utils.GlobalSpringUtils;
import com.supreme.commontools.SleepUtils;
import com.supreme.threadserver.entity.CalcNumber;
import com.supreme.threadserver.entity.ThreadStore;
import com.supreme.threadserver.service.ThreadService;
import com.supreme.threadserver.threadpkg.*;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.springframework.web.bind.annotation.*;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

@RestController
@RequestMapping(value = "thread")
public class ThreadController {

    private final Map<String, ThreadStore> threadStoreMap = new ConcurrentHashMap<>();

    public static final String THREAD_PKG_PATH = "com.supreme.threadserver.thread.";

    @GetMapping(value = "loopThread")
    public AjaxResp loopThread(@RequestParam(value = "loopCount") Integer loopCount, @RequestParam(value = "timeout") Long timeout) {
        final LoopThread loopThread = new LoopThread(loopCount, timeout);
        final Thread thread = new Thread(loopThread);
        thread.start();
        return AjaxResp.instance();
    }

    @GetMapping(value = "threadList")
    public AjaxResp threadList() {
        final List<String> threadList = new ArrayList<>(threadStoreMap.keySet());
        return AjaxResp.instance(threadList);
    }

    @PostMapping(value = "createThread")
    public AjaxResp createThread(@RequestParam(value = "threadType", defaultValue = "SimpleThread") String threadType,
                                 @RequestParam(value = "threadName") String threadName,
                                 @RequestParam(value = "start", defaultValue = "false") Boolean start) {
        if (threadStoreMap.containsKey(threadName)) {
            return AjaxResp.instance(-1, "thread exist");
        }

        try {
            Class<?> clazz = Class.forName(THREAD_PKG_PATH + threadType);
            Constructor<?> accessibleConstructor = ConstructorUtils.getAccessibleConstructor(clazz);
            Object newInstance = accessibleConstructor.newInstance();

            if (newInstance instanceof ConsumerHandler) {
                MethodUtils.invokeMethod(newInstance, "setConsumer", (Consumer<Boolean>) isFinish -> {
                    if (isFinish) {
                        threadStoreMap.remove(threadName);
                        System.out.println("thread removed");
                    }
                });
            }

            if (newInstance instanceof Runnable) {
                Runnable runnable = (Runnable) newInstance;
                Thread thread = new Thread(runnable, threadName);
                ThreadStore threadStore = new ThreadStore()
                        .setTask(runnable)
                        .setThread(thread);
                threadStoreMap.put(threadName, threadStore);

                if (start) {
                    thread.start();
                }
            }
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            return AjaxResp.instance(e.getClass().getSimpleName());
        } catch (InvocationTargetException | NoSuchMethodException e) {
            throw new RuntimeException(e);
        }

        return AjaxResp.instance();
    }

    @PostMapping(value = "startThread")
    public AjaxResp startThread(@RequestParam(value = "name") String name) {
        ThreadStore threadStore = threadStoreMap.get(name);
        if (Objects.isNull(threadStore)) {
            return AjaxResp.instance(-1, "thread not found");
        }

        threadStore.getThread().start();
        return AjaxResp.instance();
    }

    @PostMapping(value = "interruptThread")
    public AjaxResp interruptThread(@RequestParam(value = "name") String name) {
        ThreadStore threadStore = threadStoreMap.get(name);
        if (Objects.isNull(threadStore)) {
            return AjaxResp.instance(-1, "thread not found");
        }

        threadStore.getThread().interrupt();
        return AjaxResp.instance();
    }

    @PostMapping(value = "stopThread")
    public AjaxResp stopThread(@RequestParam(value = "name") String name) {
        ThreadStore threadStore = threadStoreMap.get(name);
        if (Objects.isNull(threadStore)) {
            return AjaxResp.instance(-1, "thread not found");
        }

        Runnable runnableTask = threadStore.getTask();
        if (runnableTask instanceof VolatileThread) {
            VolatileThread volatileThread = (VolatileThread) runnableTask;
            volatileThread.setStopThread(true);
        }
        return AjaxResp.instance();
    }

    @PostMapping(value = "testVolatileThread")
    public AjaxResp testVolatileThread() {
        final CalcNumber calcNumber = new CalcNumber();
        Thread thread = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            calcNumber.setMarkNum(1);
        });
        thread.start();

        while (calcNumber.getMarkNum() == 0) {

        }

        System.out.println("testVolatileThread");
        return AjaxResp.instance();
    }

    private Integer calcNumCount = 0;

    @PostMapping(value = "testVolatileCalcNumCount")
    public AjaxResp testVolatileCalcNumCount() {
        calcNumCount = 0;

        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 50; i++) {
                calcNumCount++;
            }
        });
        thread1.start();

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 50; i++) {
                calcNumCount++;
            }
        });
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println("testVolatileCalcNumCount:" + calcNumCount);
        return AjaxResp.instance();
    }

    @PostMapping(value = "sleepThread")
    public AjaxResp sleepThread(@RequestParam(value = "loopCount") int loopCount,
                                @RequestParam(value = "timeout") long timeout) {
        final SleepThread sleepThread = new SleepThread(timeout);
        for (int i = 0; i < loopCount; i++) {
            final Thread thread = new Thread(sleepThread);
            thread.start();
        }
        return AjaxResp.instance();
    }

    @PostMapping(value = "waitThread")
    public AjaxResp waitThread(@RequestParam(value = "loopCount") int loopCount,
                               @RequestParam(value = "timeout") long timeout) {
        final WaitThread waitThread = new WaitThread(timeout);
        for (int i = 0; i < loopCount; i++) {
            final Thread thread = new Thread(waitThread);
            thread.start();
        }
        return AjaxResp.instance();
    }

    @GetMapping(value = "testSleep")
    public AjaxResp testSleep(@RequestParam(value = "sleepSecond", defaultValue = "10") long sleepSecond) {
        long currentTimeMillis = System.currentTimeMillis();
        SleepUtils.sleep(sleepSecond);
        long totalSecond = (System.currentTimeMillis() - currentTimeMillis) / 1000;
        return AjaxResp.instance(totalSecond);
    }

    @GetMapping(value = "lockMethod")
    public AjaxResp lockMethod() {
        CompletableFuture.runAsync(() -> GlobalSpringUtils.getBean(ThreadService.class).lockMethod());
        return AjaxResp.instance();
    }

    @GetMapping(value = "waitMethod")
    public AjaxResp waitMethod() {
        CompletableFuture.runAsync(() -> GlobalSpringUtils.getBean(ThreadService.class).waitMethod());
        return AjaxResp.instance();
    }

    @GetMapping(value = "notifyMethod")
    public AjaxResp notifyMethod() {
        CompletableFuture.runAsync(() -> GlobalSpringUtils.getBean(ThreadService.class).notifyMethod());
        return AjaxResp.instance();
    }

    @GetMapping(value = "notifyAllMethod")
    public AjaxResp notifyAllMethod() {
        CompletableFuture.runAsync(() -> GlobalSpringUtils.getBean(ThreadService.class).notifyAllMethod());
        return AjaxResp.instance();
    }
}
